#region Copyright & License

//
// Copyright 2001-2005 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#endregion

using System;
using System.Collections;
using log4net.Repository;

namespace log4net.Core {

  #region WrapperCreationHandler

  /// <summary>
  /// Delegate used to handle creation of new wrappers.
  /// </summary>
  /// <param name="logger">The logger to wrap in a wrapper.</param>
  /// <remarks>
  /// <para>
  /// Delegate used to handle creation of new wrappers. This delegate
  /// is called from the <see cref="WrapperMap.CreateNewWrapperObject"/>
  /// method to construct the wrapper for the specified logger.
  /// </para>
  /// <para>
  /// The delegate to use is supplied to the <see cref="WrapperMap"/>
  /// constructor.
  /// </para>
  /// </remarks>
  public delegate ILoggerWrapper WrapperCreationHandler(ILogger logger);

  #endregion WrapperCreationHandler

  /// <summary>
  /// Maps between logger objects and wrapper objects.
  /// </summary>
  /// <remarks>
  /// <para>
  /// This class maintains a mapping between <see cref="ILogger"/> objects and
  /// <see cref="ILoggerWrapper"/> objects. Use the <see cref="GetWrapper"/> method to 
  /// lookup the <see cref="ILoggerWrapper"/> for the specified <see cref="ILogger"/>.
  /// </para>
  /// <para>
  /// New wrapper instances are created by the <see cref="CreateNewWrapperObject"/>
  /// method. The default behavior is for this method to delegate construction
  /// of the wrapper to the <see cref="WrapperCreationHandler"/> delegate supplied
  /// to the constructor. This allows specialization of the behavior without
  /// requiring subclassing of this type.
  /// </para>
  /// </remarks>
  /// <author>Nicko Cadell</author>
  /// <author>Gert Driesen</author>
  public class WrapperMap {
    #region Public Instance Constructors

    /// <summary>
    /// Initializes a new instance of the <see cref="WrapperMap" />
    /// </summary>
    /// <param name="createWrapperHandler">The handler to use to create the wrapper objects.</param>
    /// <remarks>
    /// <para>
    /// Initializes a new instance of the <see cref="WrapperMap" /> class with 
    /// the specified handler to create the wrapper objects.
    /// </para>
    /// </remarks>
    public WrapperMap(WrapperCreationHandler createWrapperHandler) {
      m_createWrapperHandler = createWrapperHandler;

      // Create the delegates for the event callbacks
      m_shutdownHandler = ILoggerRepository_Shutdown;
    }

    #endregion Public Instance Constructors

    #region Public Instance Properties

    /// <summary>
    /// Gets the wrapper object for the specified logger.
    /// </summary>
    /// <returns>The wrapper object for the specified logger</returns>
    /// <remarks>
    /// <para>
    /// If the logger is null then the corresponding wrapper is null.
    /// </para>
    /// <para>
    /// Looks up the wrapper it it has previously been requested and
    /// returns it. If the wrapper has never been requested before then
    /// the <see cref="CreateNewWrapperObject"/> virtual method is
    /// called.
    /// </para>
    /// </remarks>
    public virtual ILoggerWrapper GetWrapper(ILogger logger) {
      // If the logger is null then the corresponding wrapper is null
      if (logger == null)
        return null;

      lock (this) {
        // Lookup hierarchy in map.
        var wrappersMap = (Hashtable) m_repositories[logger.Repository];

        if (wrappersMap == null) {
          // Hierarchy does not exist in map.
          // Must register with hierarchy

          wrappersMap = new Hashtable();
          m_repositories[logger.Repository] = wrappersMap;

          // Register for config reset & shutdown on repository
          logger.Repository.ShutdownEvent += m_shutdownHandler;
        }

        // Look for the wrapper object in the map
        var wrapperObject = wrappersMap[logger] as ILoggerWrapper;

        if (wrapperObject == null) {
          // No wrapper object exists for the specified logger

          // Create a new wrapper wrapping the logger
          wrapperObject = CreateNewWrapperObject(logger);

          // Store wrapper logger in map
          wrappersMap[logger] = wrapperObject;
        }

        return wrapperObject;
      }
    }

    #endregion Public Instance Properties

    #region Protected Instance Properties

    /// <summary>
    /// Gets the map of logger repositories.
    /// </summary>
    /// <value>
    /// Map of logger repositories.
    /// </value>
    /// <remarks>
    /// <para>
    /// Gets the hashtable that is keyed on <see cref="ILoggerRepository"/>. The
    /// values are hashtables keyed on <see cref="ILogger"/> with the
    /// value being the corresponding <see cref="ILoggerWrapper"/>.
    /// </para>
    /// </remarks>
    protected Hashtable Repositories {
      get { return m_repositories; }
    }

    #endregion Protected Instance Properties

    #region Protected Instance Methods

    /// <summary>
    /// Creates the wrapper object for the specified logger.
    /// </summary>
    /// <param name="logger">The logger to wrap in a wrapper.</param>
    /// <returns>The wrapper object for the logger.</returns>
    /// <remarks>
    /// <para>
    /// This implementation uses the <see cref="WrapperCreationHandler"/>
    /// passed to the constructor to create the wrapper. This method
    /// can be overridden in a subclass.
    /// </para>
    /// </remarks>
    protected virtual ILoggerWrapper CreateNewWrapperObject(ILogger logger) {
      if (m_createWrapperHandler != null)
        return m_createWrapperHandler(logger);
      return null;
    }

    /// <summary>
    /// Called when a monitored repository shutdown event is received.
    /// </summary>
    /// <param name="repository">The <see cref="ILoggerRepository"/> that is shutting down</param>
    /// <remarks>
    /// <para>
    /// This method is called when a <see cref="ILoggerRepository"/> that this
    /// <see cref="WrapperMap"/> is holding loggers for has signaled its shutdown
    /// event <see cref="ILoggerRepository.ShutdownEvent"/>. The default
    /// behavior of this method is to release the references to the loggers
    /// and their wrappers generated for this repository.
    /// </para>
    /// </remarks>
    protected virtual void RepositoryShutdown(ILoggerRepository repository) {
      lock (this) {
        // Remove the repository from map
        m_repositories.Remove(repository);

        // Unhook events from the repository
        repository.ShutdownEvent -= m_shutdownHandler;
      }
    }

    /// <summary>
    /// Event handler for repository shutdown event.
    /// </summary>
    /// <param name="sender">The sender of the event.</param>
    /// <param name="e">The event args.</param>
    void ILoggerRepository_Shutdown(object sender, EventArgs e) {
      var repository = sender as ILoggerRepository;
      if (repository != null) // Remove all repository from map
        RepositoryShutdown(repository);
    }

    #endregion Protected Instance Methods

    #region Private Instance Variables

    /// <summary>
    /// The handler to use to create the extension wrapper objects.
    /// </summary>
    readonly WrapperCreationHandler m_createWrapperHandler;

    /// <summary>
    /// Map of logger repositories to hashtables of ILogger to ILoggerWrapper mappings
    /// </summary>
    readonly Hashtable m_repositories = new Hashtable();

    /// <summary>
    /// Internal reference to the delegate used to register for repository shutdown events.
    /// </summary>
    readonly LoggerRepositoryShutdownEventHandler m_shutdownHandler;

    #endregion Private Instance Variables
  }
}